package com.cycleit.cyclevalidator {
	import flash.display.DisplayObjectContainer;
	import flash.utils.Dictionary;
	
	import mx.collections.ArrayCollection;
	import mx.core.UIComponent;
	import mx.events.ValidationResultEvent;
	import mx.resources.ResourceManager;
	import mx.validators.DateValidator;
	import mx.validators.EmailValidator;
	import mx.validators.NumberValidator;
	import mx.validators.StringValidator;
	import mx.validators.ValidationResult;
	import mx.validators.Validator;

	/**
	 * Esta clase centraliza los validators creando una única instancia por
	 * tipo de validación.
	 *
	 * Suscribiremos los distintos controles que queramos validar de un formulario, indicando el tipo de
	 * validación que queramos aplicar. Invocaremos el método validate pasándole un Form como argumento para que sea
	 * validado.
	 *
	 * */
	public class CycleValidator {
		//Ctes relacionadas con los distintos tipos de validators
		//habrá una constante por cada tipo de validador
		public static const MANDATORY_TEXT:String = "textoObligatorio";
		public static const VALID_DATE:String = "fechaCorrecta";
		public static const MANDATORY_SELECTION_TEXT:String = "seleccionLabelObligatoria";
		public static const MANDATORY_SELECTION_INDEX:String = "seleccionIndexObligatoria";
		public static const INCORRECT_EMAIL:String = "emailIncorrecto";
		public static const INCORRECT_EMAIL_LIST:String = "listaEmailIncorrecta";
		public static const INCORRECT_MANDATORY_EMAIL_LIST:String = "listaEmailIncorrectaObligatoria";
		public static const MIN_VALUE_ZERO:String = "minimoValorCero";

		// Suscripción a validación
		private var _validations:Dictionary;
		//validators
		private var _validators:Dictionary;
		//para controlar el foco en los components con error
		private var _firstErrorWithFocus:Boolean = false;

		private static var _instance:CycleValidator;

		/**
		 * Constructor de la clase, instanciará todos los _validators que tengamos hasta la fecha.
		 * */
		public function CycleValidator(c:SingleClass) {
			//instanciamos las dos estructuras que nos sirven para 
			//mantener la informacion de las _validations
			_validations = new Dictionary();
			_validators = generateValidators();
		}

		/**
		 * Método propio de la implementación del patrón singleton que devuelve
		 * una referencia a la única instancia de CycleValidator.
		 * */
		public static function getInstance():CycleValidator {
			if (!_instance) {
				_instance = new CycleValidator(new SingleClass());
			}
			return _instance;
		}

		/**
		 * Método que usaremos para validar un form.
		 * */
		public function validate(comp:DisplayObjectContainer):Boolean {
			var valid:Boolean = true;
			var formValidations:ArrayCollection = _validations[comp] as ArrayCollection;
			//si teníamos controles dados de alta para este form
			//procedemos con la validación
			if (formValidations) {
				for each (var item:ValidationInfo in formValidations) {
					item.validator.source = item.component;
					var errorStrings:Array = getErrors(item.validator);
					if (errorStrings) {
						item.component.errorString = ResourceManager.getInstance().getString("validators", ValidationResult(errorStrings[0]).errorMessage);
						if (!_firstErrorWithFocus) {
							item.component.setFocus();
							_firstErrorWithFocus = true;
						}
						valid = false;
					}
					item.validator.source = null;
				}
			}

			//una vez validados los controles del Form
			//validamos los subforms que tenga.
			var childVal:Boolean = validateChilds(comp);
			if (!childVal) {
				valid = childVal;
			}
			return valid;
		}

		/**
		 * Método que utilizaremos para suscribir de un form, un control, con un validador específico.
		 * */
		public function addValidation(form:DisplayObjectContainer, comp:UIComponent, key:String):void {
			var components:ArrayCollection = _validations[form] as ArrayCollection;
			if (!components) {
				components = new ArrayCollection();
				_validations[form] = components;
			}
			var elm:ValidationInfo = new ValidationInfo(comp, Validator(_validators[key]));
			components.addItem(elm);
		}

		/**
		 * Método que utilizaremos para eliminar la suscripción de un form, un control, con un validador específico.
		 * */
		public function removeValidation(form:DisplayObjectContainer, comp:UIComponent):void {
			var components:ArrayCollection = _validations[form] as ArrayCollection;
			if (components) {
				var found:Boolean = false;
				var i:uint = 0;
				while ((i < components.length) && (!found)) {
					var vi:ValidationInfo = ValidationInfo(components.getItemAt(i));
					if (comp == vi.component) {
						components.removeItemAt(i);
						vi.component = null;
						vi.validator = null;
						found = true;
					} else {
						i++;
					}
				}
			}
		}

		/**
		 * Método que utilizaremos para eliminar la suscripción a un form al completo.
		 * */
		public function removeAllValidations(form:DisplayObjectContainer):void {
			var components:ArrayCollection = _validations[form] as ArrayCollection;
			if (components) {
				//eliminamos los _validators del hash
				_validations[form] = null;
				//desvinculamos los ValidationInfo de los components
				var i:uint = 0;
				while (i < components.length) {
					var elm:ValidationInfo = ValidationInfo(components.getItemAt(i));
					elm.component = null;
					elm.validator = null;

					i++;
				}
				components.removeAll();
			}
		}

		/**
		 * Método que utilizaremos para limpiar las validaciones de un form y
		 * todos los validadores de los forms hijos de este.
		 * */
		public function cleanAllValidations(form:DisplayObjectContainer):void {
			var components:ArrayCollection = _validations[form] as ArrayCollection;
			if (components) {
				//vaciamos la cadena de error de los components
				var i:uint = 0;
				while (i < components.length) {
					var elm:ValidationInfo = ValidationInfo(components.getItemAt(i));
					elm.component.errorString = "";

					i++;
				}
			}
			cleanChilds(form);
		}

		//Método usado para validar el control en última instancia
		private function getErrors(val:Validator):Array {
			var event:ValidationResultEvent = ValidationResultEvent(val.validate());
			if (event.type == ValidationResultEvent.VALID) {
				return null;
			}
			return event.results;
		}

		//Método que recorrerá los hijos de un form buscando contenedores
		//suceptibles de ser validados iterativamente
		private function validateChilds(component:DisplayObjectContainer):Boolean {
			var val:Boolean = true;
			for (var i:uint = 0; i < component.numChildren; i++) {
				var elm:DisplayObjectContainer = component.getChildAt(i) as DisplayObjectContainer;
				if (elm) {
					var childVal:Boolean = validate(elm);
					if (!childVal) {
						val = childVal;
					}
				}
			}
			_firstErrorWithFocus = false;
			return val;
		}

		//Método que recorrerá los hijos de un form para limpiar
		//la validacion de forma iterativa
		private function cleanChilds(comp:DisplayObjectContainer):void {
			for (var i:uint = 0; i < comp.numChildren; i++) {
				var elm:DisplayObjectContainer = comp.getChildAt(i) as DisplayObjectContainer;
				if (elm) {
					cleanAllValidations(elm);
				}
			}
		}

		//Metodo encargado de inicializar la estructura de _validators
		//y en el que instanciaremos todos los _validators que tiene nuestra aplicación
		private function generateValidators():Dictionary {
			var res:Dictionary = new Dictionary();

			var validadorObligatorio:StringValidator = new StringValidator();
			validadorObligatorio.required = true;
			validadorObligatorio.property = "text";
			validadorObligatorio.requiredFieldError = "requiredFieldError";

			res[MANDATORY_TEXT] = validadorObligatorio;

			var validadorFecha:DateValidator = new DateValidator();
			validadorFecha.required = true;
			validadorFecha.property = "selectedDate";
			validadorFecha.requiredFieldError = "requiredFieldError";

			res[VALID_DATE] = validadorFecha;

			var validadorComboLabel:StringValidator = new StringValidator();
			validadorComboLabel.required = true;
			validadorComboLabel.property = "selectedLabel";
			validadorComboLabel.requiredFieldError = "selectedLabelRequired";

			res[MANDATORY_SELECTION_TEXT] = validadorComboLabel;
			
			var validadorComboIndex:NumberValidator = new NumberValidator();
			validadorComboIndex.required = true;
			validadorComboIndex.property = "selectedIndex";
			validadorComboIndex.allowNegative = false;
			validadorComboIndex.requiredFieldError = "requiredFieldError";

			res[MANDATORY_SELECTION_INDEX] = validadorComboIndex;

			var validadorEmail:EmailValidator = new EmailValidator();
			validadorEmail.required = true;
			validadorEmail.property = "text";
			validadorEmail.requiredFieldError = "requiredFieldError";
			validadorEmail.invalidCharError = "invalidCharErrorEV";
			validadorEmail.invalidDomainError = "invalidDomainErrorEV";
			validadorEmail.invalidIPDomainError = "invalidIPDomainError";
			validadorEmail.invalidPeriodsInDomainError = "invalidPeriodsInDomainError";
			validadorEmail.missingAtSignError = "missingAtSignError";
			validadorEmail.missingPeriodInDomainError = "missingPeriodInDomainError";
			validadorEmail.missingUsernameError = "missingUsernameError";
			validadorEmail.tooManyAtSignsError = "tooManyAtSignsError";

			res[INCORRECT_EMAIL] = validadorEmail;

			var validadorEmailList:EmailListValidator = new EmailListValidator();
			validadorEmailList.required = false;
			validadorEmailList.property = "text";
			validadorEmailList.requiredFieldError = "requiredFieldError";
			validadorEmailList.emailErrorString = "someErrorEmails";

			res[INCORRECT_EMAIL_LIST] = validadorEmailList;

			var validadorEmailListOb:EmailListValidator = new EmailListValidator();
			validadorEmailListOb.required = true;
			validadorEmailListOb.property = "text";
			validadorEmailListOb.requiredFieldError = "requiredFieldError";
			validadorEmailListOb.emailErrorString = "someErrorEmails";

			res[INCORRECT_MANDATORY_EMAIL_LIST] = validadorEmailListOb;

			var validadorCero:NumberValidator = new NumberValidator();
			validadorCero.required = true;
			validadorCero.minValue = 0;
			validadorCero.property = "value";
			validadorCero.requiredFieldError = "requiredFieldError";
			validadorCero.lowerThanMinError = "lowerThanMinError";

			res[MIN_VALUE_ZERO] = validadorCero;

			return res;
		}
	}
}

class SingleClass {
}